It is all to easy to build a database on a server and then expect it to be fully functional. Not so fast: There are a number of possibilities in terms of server-based functionality that can defeat you. Databases are likely to have scheduled jobs and alerts, and may have server triggers or even message queues. They are going to have different credentials and logins, proxy accounts, shared schedules and so on. Database applications will sometimes consist of several database components or may require linked servers to be set up. For this reason, it is important to be certain what this server-based functionality is, where it is, and to ensure that it is scripted out into source control so it can be used when provisioning a server for the application.
I’ve written in the past about how to script out server code. However, there is a different problem which is to work out the difference in configuration between two servers. Once you know the differences between the server configuration of a SQL Server instance that holds a working database application and the instance on which you wish to build a version of the application, then you can produce the relevant scripts to provide the database with the required working environment. This is a fairly common DevOps requirement, but one that seems to have few tools to help with the task. The possible reasons for this become apparent as soon as you look at the settings, properties and attributes of the server. Not only are there a great number of them, but few are likely to be relevant. When you are just starting out with this problem, It is much better to have an overview of the differences rather than become overwhelmed with a tsunami of possibly irrelevant data.
To get an overview, I prefer to examine what SMO regards as the server collections. These include such things as databases, endpoints, agent jobs, alerts and linked servers. The simplest comparisons are on the actual names. This tells you whether the two servers have, for example, a linked server of the same name. It is a start, but two servers can have, say, the same agent job with the same name, but that do different things, or are at different versions. You will still need to script them out and check that the scripts are the same, though you should then beware of false negatives due to headers with dates in them.-scripts will show up as different when the difference is actually just the date you did the scripting!
There is an important distinction to be made. The server can consist of a range of objects, such as a database. If we examine the name, we can tell fairly well what databases should be on the server we are provisioning. However, if you compare two databases, you can say whether the names are the same or different, but we are not saying whether the databases with the same name are identical or different. It is the same with settings: we can say that servers have the same settings, but they may have wildly different values. Now the fact that they have different values may, or may not, be important to the provisioning process. It would be foolhardy to say what is important because that will depend on your circumstances. After all, the fact that the server has a different name is very unlikely to be interesting.
We therefore will concentrate, in this script, on comparing the names of the objects in the various collections. This will at least tell you if a component is missing and is a lot quicker than tooling about with SSMS’s object browser!
For this work, there is a useful built-in Cmdlet called Compare-Object. Once you’ve understood the way it works it is very handy for doing comparisons. Its only problem is that it is not the most intuitive visual way of reporting differences, so we use the Compare-Object’s ‘SideIndicator’ for building up the results and then convert the results in one go, into something that is easier to understand. Note that I’m not listing objercts that are equal, just those that exist only on one server or the other. That is a knob you can twiddle but I’d only do that where there are likely to be just a limited number of objects.
Obviously, this would be wrapped into a function in general use. I’ve unwrapped it here to make it easier to investigate.
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153  | 
						$Data = @{ "source" = @{     #this is the source server server you are comparing     'Server' = 'MySourceServer'; #The SQL Server instance     'instance' = '\'     'username' = 'ArthurGig'; #leave blank if windows authentication   } "target" = @{     #this is the server you are comparing it with     'Server' = 'MyTargetServer'; #The SQL Server instance     'instance' = '\'     'username' = 'PhilipJFactor'; #leave blank if windows authentication   } } $BadChars = '[\\\/\:\.]' #characters that we don't want in filenames set-psdebug -strict # to catch subtle errors $ErrorActionPreference = "stop" # you can opt to stagger on, bleeding, if an error occurs # Load sqlserver module $popVerbosity = $VerbosePreference #remember current verbosity setting $VerbosePreference = "Silentlycontinue" # the import process is very noisy if you are in verbose mode Import-Module sqlserver -DisableNameChecking #load the SQLPS functionality if ([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Management.XEvent") -eq $null) {   throw "Could not load library for Extended Events." } $VerbosePreference = $popVerbosity <#  ----  now pick up the credentials if using SQL Server Authentication #> @($Data.source,$Data.target)|foreach{ # get credentials if necessary     if ($_.username -ne '') #then it is using SQL Server Credentials     { #have we got them stored locally       $SqlEncryptedPasswordFile = `       "$env:USERPROFILE\$($_.username)-$($_.Server+$_.Instance -replace $BadChars, '').txt"       # test to see if we know about the password in a secure string stored in the user area       if (Test-Path -path $SqlEncryptedPasswordFile -PathType leaf)       {         #has already got this set for this login so fetch it         $Sqlencrypted = Get-Content $SqlEncryptedPasswordFile | ConvertTo-SecureString         $SqlCredentials = `         New-Object System.Management.Automation.PsCredential($SqlUserName, $Sqlencrypted)       }       else #then we have to ask the user for it       {         #hasn't got this set for this login         $SqlCredentials = get-credential -Credential $SqlUserName         $SqlCredentials.Password | ConvertFrom-SecureString |         Set-Content $SqlEncryptedPasswordFile             }     $_.Credentials=$SqlCredentials #save them with the server data     } } $ms = 'Microsoft.SqlServer' $My = "$ms.Management.Smo" # <# now we use the information we have and the credentials to connect  to the servers #> @($Data.source,$Data.target)|foreach{         if ($_.username -eq '') #dead simple if using windows security     { $s = new-object ("$My.Server") $_.Server+$_.Instance  }     else # if using sql server security we do it via a connection object     {       $ServerConnection = new-object "$ms.Management.Common.ServerConnection" (         "$($_.Server)$($_.Instance)" , $_.username, $_.Credentials.Password)       $s = new-object ("$My.Server") $ServerConnection     }     $_.ServerObject=$s } $sourceName=$Data.source.ServerObject.Name #for the result headings $TargetName=$Data.Target.ServerObject.Name #for the result headings <# now we start by collecting all the possible properties, excluding any that we know give trouble. and we go through each collection, comparing the names between the two servers #> $ComparisonList=$Data.source.ServerObject|gm -MemberType 'property'|  where {$_.definition -like '*collection*'}| #all the collection objects    select name | #filter out the ones that cause problems      where {$_.Name -notin @('SystemMessages','OleDbProviderSettings')}|        foreach  { #for each collection name ...      $currentType=$_.Name #we now get a list of objects for each      $sourceList=$Data.source.ServerObject.$currentType|select name      $TargetList=$Data.target.ServerObject.$currentType|select name      #we check that they both have objects in the list      if ($SourceList -ne $null -and $TargetList -ne $null)          {Compare-Object $sourceList $TargetList -Property name|            select name,SideIndicator, @{Name="Type"; Expression = {$CurrentType}}}      elseif ($TargetList -ne $null) # well it is easy, only in the source          { $TargetList|            select Name,               @{Name="SideIndicator"; Expression = {'=>'}},              @{Name="Type"; Expression = {$CurrentType}}}      elseif ($SourceList -ne $null) # we know they are only in the target          { $SourceList|            select Name,               @{Name="SideIndicator"; Expression = {'<='}},               @{Name="Type"; Expression = {$CurrentType}}} } #Now we get all the collections in the jobserver $ComparisonList+=$Data.source.ServerObject.Jobserver|gm -MemberType 'property'|  where {$_.definition -like '*collection*'} | #get all the jobserver collection objects # all the Agent objects we want to script out     Foreach {      $currentType=$_.Name #get a list of all the objects      $sourceList=$Data.source.ServerObject.JobServer.$currentType|select name      $TargetList=$Data.target.ServerObject.JobServer.$currentType|select name      if ($SourceList -ne $null -and $TargetList -ne $null)          {Compare-Object $sourceList $TargetList -Property name|            select name,SideIndicator, @{Name="Type"; Expression = {$CurrentType}}}      elseif ($TargetList -ne $null)           { $TargetList|            select Name,               @{Name="SideIndicator"; Expression = {'=>'}},              @{Name="Type"; Expression = {$CurrentType}}}      elseif ($SourceList -ne $null)           { $SourceList|            select Name,               @{Name="SideIndicator"; Expression = {'<='}},               @{Name="Type"; Expression = {$CurrentType}}} } # finally, we pick up the XEvents which are stored separately $Data.source.SqlConn = $Data.source.ServerObject.ConnectionContext.SqlConnectionObject $Data.Target.SqlConn = $Data.Target.ServerObject.ConnectionContext.SqlConnectionObject $Data.Source.XEstore = New-Object  Microsoft.SqlServer.Management.XEvent.XEStore $Data.Source.SqlConn $Data.Target.XEstore = New-Object  Microsoft.SqlServer.Management.XEvent.XEStore $Data.Target.SqlConn $sourceList=$Data.source.XEStore.Sessions | select name $TargetList=$Data.Target.XEStore.Sessions | select name if ($SourceList -ne $null -and $TargetList -ne $null)          {$ComparisonList+= Compare-Object $sourceList $TargetList -Property name|            select name,SideIndicator, @{Name="Type"; Expression = {'Xevent Sessions'}}}      elseif ($TargetList -ne $null)           { $ComparisonList+= $TargetList|            select Name,               @{Name="SideIndicator"; Expression = {'=>'}},              @{Name="Type"; Expression = {'Xevent Sessions'}}}      elseif ($SourceList -ne $null)           { $ComparisonList+= $SourceList|            select Name,               @{Name="SideIndicator"; Expression = {'<='}},               @{Name="Type"; Expression = {'Xevent Sessions'}}} <# Now we have the entire list we then list them out.#> $ComparisonList|   select @{Name=$sourceName; Expression = {if ($_.sideIndicator -eq '<=') {$_.Name} else {''}}},           @{Name=$TargetName; Expression = {if ($_.sideIndicator -eq '=>') {$_.Name} else {''}}},          @{Name="Type"; Expression = {$_.Type}}  | 
					
Here is a result from running it on a couple of development servers. The first two columns have the names of the two servers at the top, and the third column has the type of object we’re investigating
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59  | 
						DeepThink\              BigThought\                   Type            ----------              -----------                   ----                                    Nell                          Credentials                             Dan McGrew                    Credentials                             Abednego                      Databases                               Antipas                       Databases                               Archaelus                     Databases                               Daniel                        Databases                               Meshach                       Databases                               RedGateMonitor                Databases                               ServerEvents                  Databases                               WebsiteUsage                  Databases       AdventureWorks2012                                    Databases       contacts                                              Databases       Customers                                             Databases       MarineMammals                                         Databases       NorthWind                                             Databases       WSLSOURCE                                             LinkedServers   ReportingServer                                       LinkedServers                          Wheezy                        LinkedServers                              BIGTHOUGHT\Administrator      Logins                                  BIGTHOUGHT\Nell               Logins                                  BIGTHOUGHT\Posh               Logins                                  PercyTheGreenEngine           Logins          MSSecurityMtr                                         Logins          DEEPTHINK\Administrator                               Logins          DEEPTHINK\Nell                                        Logins                                  HostDistribution              Properties                              HostRelease                   Properties                              HostServicePackLevel          Properties                              HostSku                       Properties                              RG_SQLLighthouse_DDLTrigger   Triggers                                208 Error                     Alerts                                  InvalidObjectError            Alerts          Business                                              JobCategories                           CheckClones                   Jobs                                    CheckConfiguration            Jobs                                    InvalidObjectDetected         Jobs                                    RunPowerShellScript           Jobs                                    Nell                          ProxyAccounts                           PoshProxy                     ProxyAccounts                           RunItEveryMinute              SharedSchedules                         AllErrors                     Xevent Sessions                         allLogins                     Xevent Sessions                         AllSQLStatementsExecuted      Xevent Sessions                         AllWarnings                   Xevent Sessions                         BatchCompleted                Xevent Sessions                         CheckingSPsAndSQLStatements   Xevent Sessions                         MonitorErrors                 Xevent Sessions                         PermissionsErrors             Xevent Sessions                         QueryTimeouts                 Xevent Sessions                         QuickSessionStandard          Xevent Sessions                         QuickSessionTSQL              Xevent Sessions                         sqlmonitor_session            Xevent Sessions                         UncompletedQueries            Xevent Sessions                         WhoChangedWhat                Xevent Sessions MonitorSuspiciousErrors                               Xevent Sessions Recompile_Histogram                                   Xevent Sessions Recompiles                                            Xevent Sessions   | 
					
        
	
	
	
Load comments